import IndexedDBManager from './IndexedDBManager'
const getParentVisible = (item) => {
  let cur = item
  let visible = cur.visible
  while (cur.parent) {
    cur = cur.parent
    visible = visible && cur.visible
  }
  return visible
}
function hasOverlayOrParent (map, item) {
  if (map.has(item)) {
    return map.get(item)
  } else if (item.parent) {
    return hasOverlayOrParent(map, item.parent)
  }
}

class RenderCalls {
  constructor () {
    this.id = 0
    this.data = new Map()
  }

  add (cb) {
    if (!cb) return
    this.id++
    this.data.set(this.id, cb)
    return this.id
  }

  remove (id) {
    if (this.data.get(id)) {
      this.data.delete(id)
    }
  }

  clear () {
    this.data.clear()
  }

  forEach (cb) {
    this.data.values().forEach(cb)
  }
}

export default class {
  constructor (elemId) {
    this.container = document.getElementById(elemId)
    this._width = this.container.offsetWidth
    this._height = this.container.offsetHeight

    this.scene = new THREE.Scene()

    this.renderer = new THREE.WebGLRenderer({
      alpha: true,
      antialias: true,
      precision: 'highp',
      logarithmicDepthBuffer: true // 对数深度缓冲区
      // preserveDrawingBuffer: true,

    })
    this.renderer.shadowMap.enabled = true
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap

    this.renderer.setSize(this._width, this._height) // 设置渲染的尺寸大小
    this.renderer.setPixelRatio(window.devicePixelRatio) // 渲染器的像素比
    this.renderer.clearColor()
    // this.scene.background = new THREE.Color(0x000000)
    // this.scene.fog = new THREE.Fog(0x0b1430, 0, 10000)
    this.container.appendChild(this.renderer.domElement)

    this.camera = new THREE.PerspectiveCamera(45, this._width / this._height, 0.1, 10000000)
    this.camera.position.set(0, 200, 200)

    this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement)
    // this.controls.minDistance = 5
    // this.controls.maxDistance = 4000
    // this.controls.autoRotate = true

    // const axesHelper = new THREE.AxesHelper(50)
    // this.scene.add(axesHelper)

    this.registerEventHandler()

    this.renderCalls = new RenderCalls()
    this.sizeCalls = new RenderCalls()

    const objResizeObserver = new ResizeObserver((entries) => {
      if (this.resizeTimer) {
        clearTimeout(this.resizeTimer)
      }
      this.resizeTimer = setTimeout(() => {
        if (this.destroyed) return
        this.resize()
        this.resizeTimer = null
      }, 32)
    })
    objResizeObserver.observe(this.container)

    return new Promise(resolve => {
      this.initIndexedDB().then(() => {
        this.render()
        resolve(this)
      })
      // this.renderer.init().then(() => {
      //  this.render()
      //  resolve(this)
      // })
    })
  }

  /*
    indexedDB初始化
  */
  async initIndexedDB () {
    const indexedDBManager = new IndexedDBManager({ dbName: 'threeDB', maxAge: 30 * 60 * 1000 })
    await indexedDBManager.initDatabase()
    globalThis.indexedDBManager = indexedDBManager
  }

  registerEventHandler () {
    const eventEnum = {
      click: 'click',
      rightClick: 'contextmenu',
      doubleClick: 'dblclick'
      // leftDown: 'mousedown',
      // leftUp: 'mouseup'
    }
    /*
    3种情况
      点击空白
      点击有绑定时间的模型
      点击无绑定事件
    */
    this.eventMap = new Map()
    for (const eventType of Object.keys(eventEnum)) {
      this.eventMap.set(eventType, new Map())

      this.renderer.domElement.addEventListener(eventEnum[eventType], (e) => {
        if (!this.eventMap.get(eventType).size) return
        const markerMap = this.eventMap.get(eventType)

        const mouse = new THREE.Vector2()
        const rayCaster = new THREE.Raycaster()
        mouse.x = (e.clientX / this._width) * 2 - 1
        mouse.y = -(e.clientY / this._height) * 2 + 1
        rayCaster.setFromCamera(mouse, this.camera)
        const intersects = rayCaster.intersectObjects(this.scene.children)
          .filter(({ object }) => !object.isSky && getParentVisible(object))

        if (intersects.length > 0) {
          const pickedObject = intersects[0]
          const overlay = pickedObject.object
          // if (markerMap.has(overlay)) {
          if (overlay && hasOverlayOrParent(markerMap, overlay)) {
            // const callbackArr = markerMap.get(overlay)
            const callbackSet1 = hasOverlayOrParent(markerMap, overlay)
            const callbackSet2 = hasOverlayOrParent(markerMap, this.sceneSymbol)

            if (callbackSet1) {
              for (const cb of callbackSet1) {
                cb({
                  hookOperations: true, // 该对象绑定有自定义事件
                  target: overlay,
                  pickedObject,
                  point: pickedObject.point,
                  screenX: e.clientX,
                  screenY: e.clientY
                })
              }
            } else if (callbackSet2) {
              for (const cb of callbackSet2) {
                cb({
                  hookOperations: false, // 该对象没有绑定自定义事件
                  target: overlay,
                  pickedObject,
                  point: pickedObject.point,
                  screenX: e.clientX,
                  screenY: e.clientY
                })
              }
            }
          }
        } else {
          // 点击场景中空白处
          // if (markerMap.has(this.sceneSymbol)) {
          if (hasOverlayOrParent(markerMap, this.sceneSymbol)) {
            // const callbackArr = markerMap.get(this.sceneSymbol)
            const callbackArr = hasOverlayOrParent(markerMap, this.sceneSymbol)
            for (const cb of callbackArr) {
              cb({
                target: 'voidScene',
                screenX: e.clientX,
                screenY: e.clientY

              })
            }
          }
        }
      }
      )
    }
  }

  on (eventType, callback) {
    const target = this.sceneSymbol
    const handlerOfType = this.eventMap.get(eventType)
    if (handlerOfType) {
      if (!handlerOfType.has(target)) {
        handlerOfType.set(target, new Set())
      }
      const callbackMap = handlerOfType.get(target)
      callbackMap.add(callback)
    } else {
      console.error('事件类型有误')
    }
  }

  initCss3DRender () {
    this.labelRenderer = new THREE.CSS3DRenderer()
    // css3D渲染器
    this.labelRenderer.setSize(this._width, this._height)
    this.labelRenderer.domElement.style.position = 'absolute'
    this.labelRenderer.domElement.style.pointerEvents = 'none'
    this.labelRenderer.domElement.style.top = '0'
    this.container.appendChild(this.labelRenderer.domElement)
    this.renderCalls.add(() => {
      this.labelRenderer.render(this.scene, this.camera)
    })
    this.sizeCalls.add(() => {
      this.labelRenderer.setSize(this._width, this._height)
    })
  }

  resize (e) {
    this._width = this.container.offsetWidth
    this._height = this.container.offsetHeight

    this.camera.aspect = this._width / this._height
    this.camera.updateProjectionMatrix()
    this.renderer.setSize(this._width, this._height) // 更新渲染器
    this.renderer.setPixelRatio(window.devicePixelRatio) //  更新渲染器的像素比

    this.sizeCalls?.forEach((fn) => {
      fn(e)
    })
  }

  render (e) {
    if (!this.destroyed) {
      this.renderer.render(this.scene, this.camera)
      this.controls && this.controls.update()
      // this.labelRenderer.render(this.scene, this.camera)
      this.renderCalls?.forEach((fn) => {
        fn(e)
      })
      requestAnimationFrame(this.render.bind(this))
    }
  }

  initLight () {
    // const environment = new THREE.RoomEnvironment(this.renderer)
    // const pmremGenerator = new THREE.PMREMGenerator(this.renderer)
    // this.scene.environment = pmremGenerator.fromScene(environment).texture

    // // 环境光 光强影响特效
    // const ambient = new THREE.AmbientLight(0xffffff, 0.7)
    // ambient.position.set(-1000, 1000, -1000)

    // // 平行光
    // const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.6)
    // directionalLight1.position.set(-200, 1400, 800)

    // this.scene.add(ambient, directionalLight1)
    const light1 = new THREE.HemisphereLight(0xffffff, 0x444444, 1.0)
    const light2 = new THREE.DirectionalLight(0xffffff, 1.0)

    light1.position.set(0, 1, 0)
    light2.position.set(0, 1, 0)
    this.scene.add(light1, light2)
  }

  // 全局泛光
  createComposer () {
    const renderPass = new THREE.RenderPass(this.scene, this.camera)
    const params = {
      bloomStrength: 0.4, // 0.5
      bloomRadius: 0.2,
      bloomThreshold: 0.1// 0.1
    }
    const unrealBloomPass = new THREE.UnrealBloomPass(
      new THREE.Vector2(this._width, this._height), // 分辨率
      params.bloomStrength, // 泛光强度
      params.bloomThreshold, // 半径
      params.bloomRadius // 阈值
    )
    unrealBloomPass.renderToScreen = true

    const composer = new THREE.EffectComposer(this.renderer) // 用于管理后期处理效果
    composer.addPass(renderPass)
    composer.addPass(unrealBloomPass)
    composer.renderTarget1.samples = 16
    composer.renderTarget2.samples = 16

    const gui = new THREE.GUI({ width: 300 })
    gui.add(params, 'bloomStrength', 0.0, 2.0).step(0.01).name('强度').onChange(function (value) {
      unrealBloomPass.strength = Number(value)
    })
    gui.add(params, 'bloomRadius', 0.0, 2.0).step(0.01).name('半径').onChange(function (value) {
      unrealBloomPass.radius = Number(value)
    })
    gui.add(params, 'bloomThreshold', 0.0, 2.0).step(0.01).name('阈值').onChange(function (value) {
      unrealBloomPass.threshold = Number(value)
    })
    // this.renderCalls.add(() => composer.render())
  }

  flyTo ({ position, target, duration = 3, stay = 0 }) {
    const { camera, controls } = this
    return new Promise((resolve) => {
      const { x, y, z } = position

      new THREE.TWEEN.Tween({
        cameraX: camera.position.x,
        cameraY: camera.position.y,
        cameraZ: camera.position.z,
        controlsX: controls.target.x,
        controlsY: controls.target.y,
        controlsZ: controls.target.z
      }).to({
        cameraX: x,
        cameraY: y,
        cameraZ: z,
        controlsX: target.x,
        controlsY: target.y,
        controlsZ: target.z
      }, duration * 1000)
        .easing(THREE.TWEEN.Easing.Quadratic.Out)// easing 指定动画缓动函数
        .start()
        .onUpdate((e) => {
          camera.position.set(e.cameraX, e.cameraY, e.cameraZ)
          controls.target.set(e.controlsX, e.controlsY, e.controlsZ)
          controls.enabled = false
        })
        .onComplete(() => {
          setTimeout(() => {
            controls.enabled = true
            resolve()
          }, stay * 1000)
        })
      const animate = () => {
        if (!this.destroyed) {
          THREE.TWEEN.update()
          requestAnimationFrame(animate)
        }
      }
      animate()
    })
  }

  pointToXY ({ x, y, z }) {
    const point3D = new THREE.Vector3(x, y, z)
    // point3D.applyMatrix4(this.camera.matrixWorldInverse)
    // console.log(x, y, z)
    // console.log(point3D)
    const ndc = point3D.project(this.camera)
    // 将归一化设备坐标转换为屏幕坐标
    const screenX = Math.round((ndc.x + 1) * this._width / 2)
    const screenY = Math.round((1 - ndc.y) * this._height / 2)
    return { x: screenX, y: screenY }
  }

  distanceToPoint ({ x, y, z }) {
    const point3D = new THREE.Vector3(x, y, z)
    return this.camera.position.distanceTo(point3D)
  }
}
